
package domain;

import utility.BufferedReaderPlus;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Logger;

/**
 * This class takes care of individual, client requests (HTTP/HTTPS). It also takes care of parsing PHP files.
 * @author HTTPDragon Team
 * @version 1.0
 */
    class ClientHandler implements Runnable{
        private static final Logger logger = Logger.getLogger("HTTPDragon Log");
        private static final String CRLF = "\r\n";
        private Map<String,String> request;
        private Socket socket;
        private String filePath;
        private String indexFileName;
        private String phpPath;
        private ServerConfig config;
        /**
         * Contains a list of MIME-types that can be processed.
         */
        private Map<String, String> mimeTypes = new HashMap<>();
            {
            mimeTypes.put(".htm", "text/htm");
            mimeTypes.put(".html", "text/html");
            mimeTypes.put(".css", "text/css");
            mimeTypes.put(".txt", "text/plain");
            mimeTypes.put(".jpg", "image/jpeg");
            mimeTypes.put(".jpeg", "image/jpeg");
            mimeTypes.put(".gif", "image/gif");
            mimeTypes.put(".png", "image/png");
            mimeTypes.put(".pdf","application/pdf");
            mimeTypes.put(".class","application/octet-stream");
            mimeTypes.put(".ps","application/postscript");
            mimeTypes.put(".torrent","application/x-bittorrent");
            mimeTypes.put(".dvi","application/x-dvi");
            mimeTypes.put(".gz","application/x-gzip");
            mimeTypes.put(".pac","application/x-ns-proxy-autoconfig");
            mimeTypes.put(".swf","application/x-shockwave-flash");
            mimeTypes.put(".tar.gz","application/x-tgz");
            mimeTypes.put(".tgz","application/x-tgz");
            mimeTypes.put(".tar","application/x-tar");
            mimeTypes.put(".zip","application/zip");
            mimeTypes.put(".mp3","audio/mpeg");
            mimeTypes.put(".m3u","audio/x-mpegurl");
            mimeTypes.put(".wma","audio/x-ms-wma");
            mimeTypes.put(".wax","audio/x-ms-wax");
            mimeTypes.put(".ogg","application/ogg");
            mimeTypes.put(".wav","audio/x-wav");
            mimeTypes.put(".js","text/javascript");
            mimeTypes.put(".asc","text/plain");
            mimeTypes.put(".c","text/plain");
            mimeTypes.put(".cpp","text/plain");
            mimeTypes.put(".log","text/plain");
            mimeTypes.put(".conf","text/plain");
            mimeTypes.put(".text","text/plain");
            mimeTypes.put(".spec","text/plain");
            mimeTypes.put(".dtd","text/xml");
            mimeTypes.put(".xml","text/xml");
            mimeTypes.put(".mpeg","video/mpeg");
            mimeTypes.put(".mpg","video/mpeg");
            mimeTypes.put(".mov","video/quicktime");
            mimeTypes.put(".qt","video/quicktime");
            mimeTypes.put(".avi","video/x-msvideo");
            mimeTypes.put(".asf","video/x-ms-asf");
            mimeTypes.put(".asx","video/x-ms-asf");
            mimeTypes.put(".wmv","video/x-ms-wmv");
            mimeTypes.put(".bz2","application/x-bzip");
            mimeTypes.put(".tbz","application/x-bzip-compressed-tar");
            mimeTypes.put(".tar.bz2","application/x-bzip-compressed-tar");
            mimeTypes.put("", "application/octet-stream");        
        }        
         /**
          * This method returns the index file's name.
          * @return the index file's name.
          */
        public String getIndexFileName() {
            return indexFileName;
        }

        /**
         * This method sets the index file's name.
         * @param indexFileName the desired file name.
         */
        private void setIndexFileName(String indexFileName) {
            this.indexFileName = indexFileName;
        }
        /**
         * This method returns the requested file's path.
         * @return filePath
         * 
         */
        public String getFilePath() {
            return filePath;
        }
        
        /**
         * This method sets a file's path.
         * @param FilePath The file's path.
         */
        private void setFilePath(String FilePath) {
            this.filePath = FilePath;
        }
        
        /**
         * This method returns the PHP processor's path.
         * @return PHP processor's location.
         */
        private String getPhpPath() {
            return phpPath;
        }
        /**
         * This method sets the PHP processor's path.
         * @param phpPath The path to the PHP processor.
         */
        private void setPhpPath(String phpPath) {
            this.phpPath = phpPath;
        }
        /**
         * Public constructor to create a client handler.
         * @param socket Instance of Socket.
         * @param config Instance of ServerConfig.
         */
        public ClientHandler(Socket socket, ServerConfig config) {        
                this.socket = socket;
                this.config = config;
                setFilePath(config.getHttpRootPath());
                setIndexFileName(config.getHttpDefaultPage());
                setPhpPath(config.getPhpPath());                
        }
        
        //Stuur output naar client
        /**
         * Listen to the client request and responds accordingly.
         */
        @Override
        public void run() {
            OutputStream output = null;
            try{
                output = socket.getOutputStream();
                request = parseHttpRequest(socket.getInputStream());
                
                File requestedFile = new File(filePath, request.get("File"));
                switch(request.get("Request")){ // unique code per http request type
                    case "DELETE":
                        if(true)
                        throw new HTTPException(501);
                        break;
                    case "GET":
                        //nothing special needs to be done on a GET request.
                        break;
                    case "HEAD":
                        // throws new 200 exception because
                        // HEAD request only returns the header with a 200 (ok) code 
                        // when the file was found otherwise it'll get the 404 below the switch   
                        if(requestedFile.isFile() && requestedFile.canRead()){
                            throw new HTTPException(200);                     
                        }           
                        break;
                    case "OPTIONS":
                        if(true)
                        throw new HTTPException(501);
                        break; 
                    case "POST":                 
                       // if(true)
                       // throw new HTTPException(501);
                        break;
                    case "PUT":
                        if(true)
                        throw new HTTPException(501);
                        break;
                    case "TRACE":      
                        // throw forbidden, we laten de TRACE request niet door                  
                        if(true)
                        throw new HTTPException(403);
                        break;
                    default:
                        if(true)
                        throw new HTTPException(501);
                        break;
                }
                if (!requestedFile.getCanonicalPath().startsWith(filePath))
                    throw new HTTPException(404);
                if (!requestedFile.isFile() || !requestedFile.canRead())
                    throw new HTTPException(404);
                
                //Antwoord sturen naar client
                sendHttpReponse(output, "200: OK", requestedFile);
                
            } catch (HTTPException httpException){
                sendHttpReponse(output, httpException.getMessage(), null);
            } catch (IOException iOException){
                System.err.println(iOException);
            }finally{
                try{
                    socket.close();
                } catch (IOException iOException){
                    System.err.println(iOException);
                }
            }
        }
        /**
         * This method sends out a response to the client, it also checks whether or not a file is a PHP file.
         * If the requested file is a PHP file, it will be processed by the PHP processor.
         * @param out A socket's OutputStream.
         * @param message A HTTP code
         * @param file The requested file.
         */
        private void sendHttpReponse(OutputStream out, String message, File file) {
                try{            
                    if (config.isPhpRun() && file!= null && isPHP(file.getName())){
                          processPHP(out,message ,file);  
                    }else {
                        BufferedOutputStream output = new BufferedOutputStream(out) ;
                        String response = "HTTP/1.1 " + message + CRLF;
                        java.text.SimpleDateFormat dateGMTFormat = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
                        dateGMTFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
                        response += "Date: "+ dateGMTFormat.format(new Date()) + CRLF; // GMT time/date stamp formatted as requested in RFC 1945
                        response += "Server: HTTPDragon" + CRLF;
                        /*
                        response += "Content-Length: " + file.length() + CRLF;
                        response += "Last-modified: " + dateGMTFormat.format(new Date(file.lastModified()));
                        */
                        output.write(response.getBytes());

                                if (file == null){
                                    response = "Content-Type: text/plain" + CRLF + CRLF + message;
                                    output.write(response.getBytes());
                                } else {
                                    String fileType = getFileType(file);
                                    response = "Content-Type: " + fileType + CRLF + CRLF;
                                    output.write(response.getBytes());

                                    //Bestand naar bytes omzetten en sturen
                                    byte[] buffer =new byte[1024];
                                    int i;

                                    InputStream input;

                                    input = new FileInputStream(file);  
                                    while ((i = input.read(buffer)) >= 0){
                                        output.write(buffer, 0, i);
                                    }
                                    input.close();
                                }
                        output.flush();
                        }
                } catch (IOException iOException){
                    System.err.println(iOException);
                }
            
        }
        /* Key: Content
         * Request:         the HTTP request Keyword
         * HTTPVersion:     the HTTP version (ex. HTTP/1.1)
         * File:            the filepath/filename.extension
         * PostInfo:        the parameters passed in a POST HTTP request
         * 'requestWord':   the part of the request related to 'requestWord'
         */
        /**
         * This method parses the client's HTTP request. Throws an IOException in case of an error while parsing the request.
         * @param is An InputStream of data.
         * @return Map<String,String> that contains the received HTTP request.
         * @throws IOException Error while parsing.
         */
        private Map<String,String> parseHttpRequest(InputStream is) throws IOException {
                Map<String,String> resultMap = new HashMap<>();        
                String result[];
                BufferedReader br = new BufferedReaderPlus(new InputStreamReader(is));  
                String oneLine = br.readLine();
                if(oneLine==null){
                    throw new HTTPException(500); //Internal Server Error
                }else{
                    String[] tokens = oneLine.split("\\s+");
                                        
                    if (tokens.length < 2)
                        throw new HTTPException(400);
                    
                    resultMap.put("Request", tokens[0]);                        
                    resultMap.put("HTTPVersion", tokens[2]);
                    
                    String file = tokens[1];
                    if (file.endsWith("/")){
                        resultMap.put("File",file + indexFileName);
                    } else {                        
                        if (hasURLParameters(tokens[1])) {
                            //URL contains additional parameters so we store them in the hashmap.
                            resultMap.put("GetInfo",getURLParameters(tokens[1]));
                            resultMap.put("File", reformFilePath(tokens[1], "?" + resultMap.get("GetInfo")));
                        } else {
                            resultMap.put("File",file);
                        }
                    }           
                    while(br.ready()){            
                        oneLine=br.readLine(); 
                        result=oneLine.split(": ");
                        if(result.length>=2){
                            resultMap.put(result[0],result[1]);
                        }else{
                            resultMap.put("PostInfo",oneLine);
                        }
                    }
                }
                return resultMap;
        }    

        /**
         * This method determines a requested file's file type.
         * @param file A file whose file type must be determined.
         * @return The file's file type.
         */
            private String getFileType(File file){
                int position = file.getName().lastIndexOf('.');
                String extention = position >= 0 ? file.getName().substring(position) : "";
                String fileType = mimeTypes.get(extention);
                return fileType == null ? mimeTypes.get("") : fileType ;
            }
            /**
             * This method determines whether or not a file is a PHP file.
             * @param file The file that will be checked.
             * @return True in case the file was a PHP file. False in case it was not a PHP file.
             * 
             */
            public boolean isPHP(String file){
		
		int bestandPositie = (int)file.lastIndexOf(".");
		
		if(file.substring(bestandPositie+1,file.length()).equals("php")){	
                    return true;
                }else{
                    return false;
                }
		
	}
        
            /**
             * This method processes a PHP file and sends the output to the client.
             * @param out Required Socket OutputStream for parsing the PHP file.
             * @param message HTTP response code.
             * @param phpFile Requested PHP file.
             */
            public void processPHP(OutputStream out, String message,  File phpFile){
            
            BufferedOutputStream output = new BufferedOutputStream(out) ;
                
            Runtime runtime=Runtime.getRuntime();
            String content="";
            Process process = null;            
            try {   
                //poging om de get values aan ons script mee te geven
                // if(request.get("GetInfo")!=null){
                //  process = runtime.exec("\""+ getPhpPath() +"\" \""+ phpFile.getAbsolutePath() +"\" --" + request.get("GetInfo").replace("&", " --")); // --no-php-ini  	
                //}else{
                   process = runtime.exec("\""+ getPhpPath() +"\" \""+ phpFile.getAbsolutePath() +"\""); // --no-php-ini  	
                //}
                    BufferedReader PHPoutput=new BufferedReader(new InputStreamReader(process.getInputStream()));
                    
                    
                    while((content = PHPoutput.readLine())!=null){
                            output.write((content+"\r\n").getBytes());
                            output.flush();
                            if(content.startsWith("Parse error: "))
                                logger.warning("(PHP) "+content);
                            
                    }
            } catch (IOException ex) {
                System.err.println(ex);    
            }finally{
                process.destroy();
            }
        }

    private boolean hasURLParameters(String url) {     
        return url.contains("?") ? true : false;
    }

    private String getURLParameters(String url) {
        return url.substring(url.indexOf("?")+1, url.length());
    }

    private String reformFilePath(String filePath, String parameters) {
        return filePath.replace(parameters, "");
    }
}